﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Origin2.Core;
using Origin2.Framework.Managers;
using Origin2.Framework.Managers.Input;
using XNAssist;

namespace Origin2.Framework
{
    /// <summary>
    /// Represents a game using the Origin2 framework
    /// </summary>
    public class OriginGame : Game, IVariableContainer, IObjectContainer, IControllable
    {
        // TODO: REMOVE, FIND A BETTER WAY TO TRACK!
        /// <summary>
        /// The current instance of the game that is running
        /// </summary>
        /// <remarks> THIS IS TEMPORARY! We have another solution in the pipeline. </remarks>
        public static OriginGame GameInstance { get; private set; }

        /// <summary>
        /// A simple constructor
        /// </summary>
        public OriginGame()
        {
            GameInstance = this;

            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            AssetManager.ContentManager = Content;
            _variables = new Dictionary<string, object>();
            Objects = new BufferedDictionary<string, IObject>();
            _controls = new BufferedDictionary<string, Control>();
        }

        #region Default XNA

        GraphicsDeviceManager _graphics;
        SpriteBatch _spriteBatch;

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            #region Game Exit
            
            // If the variable "Should Exit" is true, then exit
            if (ContainsVariable("Should Exit"))
            {
                var exit = (bool) GetVariable("Should Exit");

                if (exit)
                    Exit();
            }

            #endregion

            KeyboardManager.Update();

            #region Game Object Updates

            // Update all of the IObjects that are also IUpdateables
            foreach (var pair in Objects)
            {
                var value = pair.Value as IOriginUpdateable;
                if (value != null)
                {
                    var cast = value;
                    cast.Update(gameTime);
                }

                if (pair.Value.MarkedForDeletion) Objects.Remove(pair.Key);
            }

            // Apply the buffer to the dictionary
            Objects.Update();

            #endregion

            #region Control Updates

            // Update all of the controls
            foreach (var pair in _controls)
            {
                pair.Value.Update(gameTime);
            }

            // Apply the buffer to the dictionary
            _controls.Update();

            #endregion

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            #region Screen Clearing

            if (ContainsVariable("Background Color"))
            {
                var bg = GetVariable("Background Color");

                if (!(bg is Color))
                    throw new Exception("The variable \"Background Color\" is not a color!");

                GraphicsDevice.Clear((Color)bg);
            }
            else
                GraphicsDevice.Clear(Color.Black);

            #endregion
            
            DrawObjects();

            base.Draw(gameTime);
        }

        /// <summary>
        /// Loops through and draws the objects: virtual so that you can replace it with your own drawing method (in case you want a camera transform, or something like that)
        /// </summary>
        public virtual void DrawObjects()
        {
            _spriteBatch.Begin();

            // TODO: Sort by Z-order?
            foreach (var drawable in Objects.Select(x => x.Value).OfType<IOriginDrawable>())
            {
                drawable.Draw(_spriteBatch);
            }

            _spriteBatch.End();
        }

        #endregion

        #region IVariableContainer Implementation

        private readonly Dictionary<string, object> _variables;  

        public void SetVariable(string key, object value)
        {
            if (_variables.ContainsKey(key))
                _variables[key] = value;
            else
                _variables.Add(key, value);
        }

        public object GetVariable(string key)
        {
            return _variables[key];
        }

        public TVariableType GetVariable<TVariableType>(string key)
        {
            return (TVariableType)GetVariable(key);
        }

        public void RemoveVariable(string key)
        {
            _variables.Remove(key);
        }

        public bool ContainsVariable(string key)
        {
            return _variables.ContainsKey(key);
        }

        public bool ContainsVariable(object value)
        {
            return _variables.ContainsValue(value);
        }

        #endregion

        #region IObjectContainer Implementation

        protected readonly BufferedDictionary<string, IObject> Objects; 

        public void AddObject(string name, IObject obj)
        {
            Objects.Add(name, obj);
        }

        public IObject GetObject(string name)
        {
            return Objects[name];
        }

        public void RemoveObject(string name)
        {
            Objects.Remove(name);
        }

        public bool ContainsObject(string name)
        {
            return Objects.ContainsKey(name);
        }

        public bool ContainsObject(IObject obj)
        {
            return Objects.ContainsValue(obj);
        }

        public string GetUniqueName(string baseName)
        {
            if (!ContainsObject(baseName)) return baseName;

            var count = 0;

            while (true)
            {
                if (!ContainsObject(baseName + count)) return baseName + count;

                count++;
            }
        }

        #endregion

        #region IControllable Implementation

        private readonly BufferedDictionary<string, Control> _controls; 

        public void AddControl(string name, Control control)
        {
            _controls.Add(name, control);
        }

        public Control GetControl(string name)
        {
            return _controls[name];
        }

        public void RemoveControl(string name)
        {
            _controls.Remove(name);
        }

        public bool ContainsControl(string name)
        {
            return _controls.ContainsKey(name);
        }

        public bool ContainsControl(Control control)
        {
            return _controls.ContainsValue(control);
        }

        public bool ContainsControlType<TControlType>()
        {
            return _controls.Any(x => x.Value.GetType() == typeof(TControlType));
        }

        #endregion
    }
}
